package com.aluka.nirvana.framework.datasource.configuration;

import com.aluka.nirvana.framework.datasource.constant.NirvanaDataSource;
import com.aluka.nirvana.framework.datasource.model.DataSourceWrap;
import com.github.pagehelper.PageInterceptor;
import com.google.common.collect.Lists;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.logging.log4j.util.Strings;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.List;
import java.util.Properties;

/**
 * Mybatis 数据源初始化配置类
 * @author gongli
 * @since 2019/12/17
 */
@Slf4j
public class MybatisConfiguration {

    @Setter
    private MybatisProperties mybatisProperties;

    private MybatisConfiguration(){}

    private static MybatisConfiguration mybatisConfiguration;

    public static MybatisConfiguration getInstance(MybatisProperties mybatisProperties){
        if(mybatisConfiguration == null) {
            mybatisConfiguration = new MybatisConfiguration();
            mybatisConfiguration.setMybatisProperties(mybatisProperties);
        }
        return mybatisConfiguration;
    }

    /**
     * 检查使用Mybatis时必要配置是否正确
     * @param dbName 数据源名称
     * @param dataSourceWrap 数据源配置信息
     * @return 正确与否
     */
    public boolean checkMybatisConfig(String dbName, DataSourceWrap dataSourceWrap){
        if(Strings.isEmpty(dataSourceWrap.getMapperLocations())){
            log.warn(">>Nirvana<< 跳过数据源[{}]Mybatis相关初始化配置，因为没有配置Mapper.xml资源路径 'nirvana.database.datasource.{}.mapperLocations'",dbName,dbName);
            return false;
        }else if(Strings.isEmpty(dataSourceWrap.getMapperPackages())){
            log.warn(">>Nirvana<< 跳过数据源[{}]Mybatis相关初始化配置，因为没有配置Mapper.java包扫描路径 'nirvana.database.datasource.{}.mapperPackages'",dbName,dbName);
            return false;
        }
        return true;
    }

    /**
     * 初始化Mybatis默认多数据源时PlatformTransactionManager配置信息
     * @param registry Bean注册类
     * @param k 数据源名称，用于注解切换时指定具体数据源
     * @param applicationContext Spring上下文
     */
    public void initPlatformTransactionManager(BeanDefinitionRegistry registry, String k,ApplicationContext applicationContext) {
        if(registry.isBeanNameInUse(k + NirvanaDataSource.MYBATIS_PLATFORM_TRANSACTION_MANAGER)){
            return;
        }
        GenericBeanDefinition platformTransactionManagerBean = new GenericBeanDefinition();
        platformTransactionManagerBean.setBeanClass(DataSourceTransactionManager.class);
        platformTransactionManagerBean.setSynthetic(true);
        DataSource dataSource = applicationContext.getBean(k + NirvanaDataSource.DATA_SOURCE, DataSource.class);
        ConstructorArgumentValues argumentValues = new ConstructorArgumentValues();
        argumentValues.addIndexedArgumentValue(0,dataSource, DataSource.class.getName());
        platformTransactionManagerBean.setConstructorArgumentValues(argumentValues);
        registry.registerBeanDefinition(k + NirvanaDataSource.MYBATIS_PLATFORM_TRANSACTION_MANAGER, platformTransactionManagerBean);
        log.debug(">>Nirvana<< 初始化数据源[{}] PlatformTransactionManager[name={}] 成功", k, k + NirvanaDataSource.MYBATIS_PLATFORM_TRANSACTION_MANAGER);
    }

    /**
     * 初始化Mybatis默认多数据源时SqlSessionTemplate配置信息
     * @param registry Bean注册类
     * @param k 数据源名称，用于注解切换时指定具体数据源
     * @param applicationContext Spring上下文
     */
    public void initSqlSessionTemplate(BeanDefinitionRegistry registry, String k,ApplicationContext applicationContext) {
        if(registry.isBeanNameInUse(k + NirvanaDataSource.SQL_SESSION_TEMPLATE)){
            return;
        }
        GenericBeanDefinition sqlSessionTemplateBean = new GenericBeanDefinition();
        sqlSessionTemplateBean.setBeanClass(SqlSessionTemplate.class);
        sqlSessionTemplateBean.setSynthetic(true);
        sqlSessionTemplateBean.setPrimary(true);
        SqlSessionFactory sqlSessionFactory = applicationContext.getBean(k + NirvanaDataSource.SQL_SESSION_FACTORY, SqlSessionFactory.class);
        ConstructorArgumentValues argumentValues = new ConstructorArgumentValues();
        argumentValues.addIndexedArgumentValue(0,sqlSessionFactory,SqlSessionFactory.class.getName());
        sqlSessionTemplateBean.setConstructorArgumentValues(argumentValues);
        registry.registerBeanDefinition(k + NirvanaDataSource.SQL_SESSION_TEMPLATE, sqlSessionTemplateBean);
        log.debug(">>Nirvana<< 初始化数据源[{}] SqlSessionTemplate[name={}] 成功", k, k + NirvanaDataSource.SQL_SESSION_TEMPLATE);
    }

    /**
     * 初始化Mybatis默认多数据源时SqlSessionFactory配置信息
     * @param registry Bean注册类
     * @param k 数据源名称，用于注解切换时指定具体数据源
     * @param v 数据源配置信息
     * @param applicationContext Spring上下文
     */
    public void initSqlSessionFactory(BeanDefinitionRegistry registry, String k, DataSourceWrap v,ApplicationContext applicationContext) {
        if(registry.isBeanNameInUse(k + NirvanaDataSource.SQL_SESSION_FACTORY)){
            return;
        }
        DataSource dataSource = applicationContext.getBean(k + NirvanaDataSource.DATA_SOURCE, DataSource.class);
        GenericBeanDefinition sqlSessionFactoryBean = new GenericBeanDefinition();
        sqlSessionFactoryBean.setBeanClass(SqlSessionFactoryBean.class);
        sqlSessionFactoryBean.setSynthetic(true);
        MutablePropertyValues mpv = sqlSessionFactoryBean.getPropertyValues();
        mpv.addPropertyValue("dataSource", dataSource);
        mpv.addPropertyValue("vfs", SpringBootVFS.class);
        Configuration configuration = mybatisProperties.getConfiguration() == null ? new Configuration() : mybatisProperties.getConfiguration();
        mpv.addPropertyValue("configuration", configuration);
        mpv.addPropertyValue("configurationProperties", mybatisProperties.getConfigurationProperties());
        mpv.addPropertyValue("typeAliasesPackage", mybatisProperties.getTypeAliasesPackage());
        mpv.addPropertyValue("typeHandlersPackage", mybatisProperties.getTypeHandlersPackage());

        try{
            // 配置PageHelper插件
            PageInterceptor pageInterceptor = new PageInterceptor();
            Binder binder = Binder.get(applicationContext.getEnvironment());
            Properties properties = binder.bind("pagehelper", Properties.class).get();
            pageInterceptor.setProperties(properties);
            mpv.addPropertyValue("plugins", new Interceptor[]{pageInterceptor});
        }catch (Exception ignore){}

        try {
            mpv.addPropertyValue("mapperLocations", new PathMatchingResourcePatternResolver().getResources(v.getMapperLocations()));
        } catch (IOException e) {
            throw new  RuntimeException(e);
        }
        registry.registerBeanDefinition(k + NirvanaDataSource.SQL_SESSION_FACTORY, sqlSessionFactoryBean);
        log.debug(">>Nirvana<< 初始化数据源[{}] SqlSessionFactory[name={}] 成功", k, k + NirvanaDataSource.SQL_SESSION_FACTORY);
    }

    /**
     * 初始化Mybatis默认多数据源时MapperScannerConfigurer配置信息
     * @param registry Bean注册类
     * @param k 数据源名称，用于注解切换时指定具体数据源
     * @param v 数据源配置信息
     * @param applicationContext Spring上下文
     */
    public void initMapperScan(BeanDefinitionRegistry registry, String k, DataSourceWrap v,ApplicationContext applicationContext) {
        if(registry.isBeanNameInUse(k + NirvanaDataSource.MAPPER_SCANNER_CONFIGURER)){
            return;
        }
        List<String> scanPackages = Lists.newArrayList(v.getMapperPackages().split(","));
        int[] i = {1};
        scanPackages.forEach(scanPackage -> {
            if(Strings.isNotBlank(scanPackage)){
                GenericBeanDefinition mapperScanBean = new GenericBeanDefinition();
                mapperScanBean.setBeanClass(MapperScannerConfigurer.class);
                mapperScanBean.setSynthetic(true);
                MutablePropertyValues mpv = mapperScanBean.getPropertyValues();
//                mpv.addPropertyValue("sqlSessionFactory", applicationContext.getBean(k + NirvanaDataSource.SQL_SESSION_FACTORY, SqlSessionFactory.class));
                mpv.addPropertyValue("sqlSessionTemplate", applicationContext.getBean(k + NirvanaDataSource.SQL_SESSION_TEMPLATE, SqlSessionTemplate.class));
                mpv.addPropertyValue("basePackage", scanPackage);
                registry.registerBeanDefinition(k + NirvanaDataSource.MAPPER_SCANNER_CONFIGURER + (i[0]++), mapperScanBean);
            }
        });
        log.debug(">>Nirvana<< 初始化数据源[{}] MapperScannerConfigurer 成功：basePackages > {}",k,v.getMapperPackages());
    }

    /**
     * 初始化使用注解驱动时Mybatis数据源相关配置项
     * @param registry Bean注册类
     * @param k 数据源名称，用于注解切换时指定具体数据源
     * @param v 数据源配置信息
     * @param applicationContext Spring上下文
     */
    public void initAnnotationMapperScan(BeanDefinitionRegistry registry, String k, DataSourceWrap v,ApplicationContext applicationContext) {
        if(!registry.isBeanNameInUse(NirvanaDataSource.SQL_SESSION_FACTORY)){
            DataSource dataSource = applicationContext.getBean(DataSource.class);
            GenericBeanDefinition sqlSessionFactoryBean = new GenericBeanDefinition();
            sqlSessionFactoryBean.setSynthetic(true);
            sqlSessionFactoryBean.setBeanClass(SqlSessionFactoryBean.class);
            MutablePropertyValues mpv = sqlSessionFactoryBean.getPropertyValues();
            String mapperLocation = "classpath*:**/*Mapper.xml";
            try {
                mpv.addPropertyValue("dataSource", dataSource);
                mpv.addPropertyValue("mapperLocations", new PathMatchingResourcePatternResolver().getResources(mapperLocation));
            } catch (IOException e) {
                throw new  RuntimeException(e);
            }
            registry.registerBeanDefinition("sqlSessionFactory", sqlSessionFactoryBean);
            log.debug(">>Nirvana<< 初始化注解驱动时Mybatis数据源[{}] AnnotationSqlSessionFactory 成功：MapperLocations > {}",k,mapperLocation);
        }
        if(!registry.isBeanNameInUse(k + NirvanaDataSource.MAPPER_SCANNER_CONFIGURER)){
            GenericBeanDefinition mapperScanBean = new GenericBeanDefinition();
            mapperScanBean.setBeanClass(MapperScannerConfigurer.class);
            mapperScanBean.setSynthetic(true);
            MutablePropertyValues mpv = mapperScanBean.getPropertyValues();
            mpv.addPropertyValue("basePackage", v.getMapperPackages());
            registry.registerBeanDefinition(k + NirvanaDataSource.MAPPER_SCANNER_CONFIGURER, mapperScanBean);
            log.debug(">>Nirvana<< 初始化注解驱动时Mybatis数据源[{}] AnnotationMapperScannerConfigurer 成功：basePackages > {}",k,v.getMapperPackages());
        }

    }
}
